package com.tian.service.impl;

import com.alibaba.fastjson.JSON;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.tian.common.CommonResult;
import com.tian.common.PageResult;
import com.tian.dto.ChargeUserLoginResDto;
import com.tian.dto.coupon.CouponCacheDto;
import com.tian.dto.coupon.UserCouponCacheDto;
import com.tian.dto.user.*;
import com.tian.entity.ChargeCoupon;
import com.tian.entity.ChargeUserCoupon;
import com.tian.entity.ChargeUserPoint;
import com.tian.enums.*;
import com.tian.mapper.ChargeUserCouponMapper;
import com.tian.mapper.ChargeUserPointMapper;
import com.tian.message.MessageReqIdPrefixConstant;
import com.tian.message.UserGetCouponAdditionalMessage;
import com.tian.producer.UserGetCouponProducer;
import com.tian.service.ChargeUserCouponService;
import com.tian.util.ChargeMarketRedisKeyPrefix;
import com.tian.util.DateUtils;
import com.tian.util.RedisConstantPre;
import com.tian.util.UserCacheUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;

/**
 * {@code @description:} 我的优惠券
 *
 * @author tianwc 公众号：Java后端技术全栈
 * 在线刷题 1200+java面试题和1000+篇技术文章：<a href="https://woaijava.cc/">博客地址</a>
 * {@code @date:} 2024/1/23 9:32
 * {@code @version:} 1.0
 */
@Slf4j
@Service
public class ChargeUserCouponServiceImpl implements ChargeUserCouponService {
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ChargeUserPointMapper chargeUserPointMapper;
    @Resource
    private ChargeUserCouponMapper chargeUserCouponMapper;
    @Resource
    private UserGetCouponProducer userGetCouponProducer;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<Boolean> add(UserCouponAddReqDto userCouponAddReqDto) {
        Integer couponId = userCouponAddReqDto.getCouponId();
        ChargeUserLoginResDto userInfoCache = UserCacheUtil.getUser();
        long userId = userInfoCache.getId();
        //领取优惠券，重点是 防止用户同一时刻多领取了，所以 我们给这个用户上锁
        RLock lock = redissonClient.getLock(RedisConstantPre.USER_INFO_ID_LOCK_PRE + userId);
        lock.lock();
        try {
            ChargeUserPoint chargeUserPoint = chargeUserPointMapper.selectByUserId(userId);
            int availablePoint = chargeUserPoint.getPoint() - chargeUserPoint.getFreeze();
            RBucket<String> couponBucket = redissonClient.getBucket(ChargeMarketRedisKeyPrefix.CHARGE_COUPON_KEY + couponId);
            CouponCacheDto couponCacheDto = JSON.parseObject(couponBucket.get(), CouponCacheDto.class);
            int needPoint = 0;
            //判断优惠券是否为积分兑换类型
            if (couponCacheDto.getSendType() == CouponSendTypeEnum.POINT.getSendType()) {
                needPoint = couponCacheDto.getPoint();
                if (availablePoint < needPoint) {
                    log.info("用户id:{}，优惠券id:{}，不满足领取条件", userId, couponId);
                    return CommonResult.failed(ResultCode.ACTIVITY_NO_ENOUGH);
                }
                chargeUserPoint.setPoint(availablePoint - needPoint);
            }
            Date expire;
            Date startTime = new Date();
            if (couponCacheDto.getValidityType() == 0) {
                Integer expireType = couponCacheDto.getExpire();
                Integer expireUnit = couponCacheDto.getExpireUnit();

                LocalDate expireDate = ChargeCouponConditionExpireUnitEnum.calculateExpireDate(LocalDate.now(), expireUnit, expireType);
                if (expireDate == null) {
                    log.info("用户id:{}，优惠券id:{}，优惠券过期时间计算失败", userId, couponId);
                    return CommonResult.failed(ResultCode.PARAMETER_ERROR);
                }
                expire = Date.from(Timestamp.valueOf(expireDate.atTime(LocalTime.MIDNIGHT)).toInstant());
            } else {
                expire = couponCacheDto.getValidityEndTime();
                startTime = couponCacheDto.getValidityStartTime();
            }

            ChargeUserCoupon chargeUserCoupon = new ChargeUserCoupon();
            chargeUserCoupon.setUserId(userId);
            chargeUserCoupon.setCouponId(couponId);
            chargeUserCoupon.setStartTime(startTime);
            chargeUserCoupon.setPeriod(expire);
            chargeUserCoupon.setStatus(UserCouponStatusEnum.INIT.getStatus());
            chargeUserCoupon.setCreateTime(new Date());
            //更新用户积分
            chargeUserPointMapper.updateByPrimaryKey(chargeUserPoint);
            //新增用户领取优惠券记录
            chargeUserCouponMapper.insert(chargeUserCoupon);

            //更新用户优惠券缓存 key=固定前缀+用户优惠券记录id
            RBucket<String> userCouponBucket = redissonClient.getBucket(ChargeMarketRedisKeyPrefix.CHARGE_USER_COUPON_KEY + chargeUserCoupon.getId());
            UserCouponCacheDto userCouponCacheDto = new UserCouponCacheDto();
            BeanUtils.copyProperties(chargeUserCoupon, userCouponCacheDto);
            userCouponBucket.set(JSON.toJSONString(userCouponCacheDto));

            //更新用户积分缓存 key=固定前缀+用户id
            RBucket<String> userPointBucket = redissonClient.getBucket(ChargeMarketRedisKeyPrefix.USER_POINT_KEY + userId);
            ChargeUserPoint chargeUserPointCache = JSON.parseObject(userPointBucket.get(), ChargeUserPoint.class);
            chargeUserPointCache.setPoint(chargeUserPoint.getPoint());
            userPointBucket.set(JSON.toJSONString(chargeUserPointCache));

            //用户领取优惠券 附加处理(用户积分变更记录新增，用户处理过期优惠券记录新增)
            UserGetCouponAdditionalMessage userGetCouponAdditionalMessage = new UserGetCouponAdditionalMessage();
            userGetCouponAdditionalMessage.setUserCouponId(chargeUserCoupon.getId());
            userGetCouponAdditionalMessage.setUserId(userId);
            userGetCouponAdditionalMessage.setType(UserUpdatePointEnum.DEDUCT.getType());
            userGetCouponAdditionalMessage.setPoint(needPoint);
            userGetCouponAdditionalMessage.setExpireDate(expire);
            userGetCouponAdditionalMessage.setModifyTime(new Date());
            userGetCouponAdditionalMessage.setReqId(MessageReqIdPrefixConstant.USER_GET_COUPON_ADDITIONAL_REQ_ID_PREFIX
                    + UUID.randomUUID() + DateUtils.formatDefaultDateMs());
            userGetCouponProducer.sendUserGetCouponAdditional(userGetCouponAdditionalMessage);
        } finally {
            lock.unlock();
        }
        return CommonResult.success(Boolean.TRUE);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public CommonResult<Boolean> use(UserCouponUpdateReqDto userCouponUpdateReqDto) {
        List<ChargeUserCoupon> chargeUserCouponList = chargeUserCouponMapper.selectByIdList(userCouponUpdateReqDto.getIdList());
        for (ChargeUserCoupon chargeUserCoupon : chargeUserCouponList) {
            RBucket<String> couponBucket = redissonClient.getBucket(ChargeMarketRedisKeyPrefix.CHARGE_COUPON_KEY + chargeUserCoupon.getCouponId());
            ChargeCoupon chargeCoupon = JSON.parseObject(couponBucket.get(), ChargeCoupon.class);
            //判断优惠券是否可以叠加
            //因为前面涉及到优惠券叠加使用，这里还可以进一步设计，该优惠券能和哪些优惠券一起使用
            //暂时想到建一张表，存入该优惠券可以一起使用的优惠券关联起来。
            if (chargeCoupon.getTogether() == 1) {
                return CommonResult.failed(ResultCode.COUPON_OVERLAY);
            }
            //判断优惠券是否已经使用
            if (chargeUserCoupon.getStatus() == UserCouponStatusEnum.USED.getStatus()
                    || chargeUserCoupon.getStatus() == UserCouponStatusEnum.PRE_USE.getStatus()) {
                return CommonResult.failed(ResultCode.COUPON_USED);
            }
            //判断优惠券是否已经过期
            if (chargeUserCoupon.getStatus() == UserCouponStatusEnum.EXPIRED.getStatus()) {
                return CommonResult.failed(ResultCode.COUPON_EXPIRED);
            }
            //判断优惠券是否已经过期
            if (chargeUserCoupon.getPeriod().before(new Date())) {
                chargeUserCoupon.setId(chargeUserCoupon.getId());
                chargeUserCoupon.setUseTime(new Date());
                chargeUserCoupon.setStatus(UserCouponStatusEnum.EXPIRED.getStatus());
                chargeUserCouponMapper.use(chargeUserCoupon);
                return CommonResult.failed(ResultCode.COUPON_EXPIRED);
            }
        }
        for (ChargeUserCoupon chargeUserCoupon : chargeUserCouponList) {
            chargeUserCoupon.setId(chargeUserCoupon.getId());
            chargeUserCoupon.setUseTime(new Date());
            chargeUserCoupon.setStatus(userCouponUpdateReqDto.getStatus());
            chargeUserCouponMapper.use(chargeUserCoupon);
        }
        return CommonResult.success(Boolean.TRUE);
    }

    @Override
    public CommonResult<PageResult<List<UserCouponPageRespDto>>> page(UserCouponPageReqDto userCouponPageReqDto) {
        Page<Object> page = PageHelper.startPage(userCouponPageReqDto.getCurrentPage(), userCouponPageReqDto.getPageSize());
        List<ChargeUserCoupon> chargeUserCouponList = chargeUserCouponMapper.page(userCouponPageReqDto);
        List<UserCouponPageRespDto> couponRespDtoList = new ArrayList<>();
        for (ChargeUserCoupon chargeUserCoupon : chargeUserCouponList) {
            UserCouponPageRespDto couponRespDto = new UserCouponPageRespDto();
            BeanUtils.copyProperties(chargeUserCoupon, couponRespDto);
            couponRespDto.setStatusStr(UserCouponStatusEnum.getDesByStatus(chargeUserCoupon.getStatus()));
            if (chargeUserCoupon.getPeriod().before(new Date())) {
                chargeUserCoupon.setId(chargeUserCoupon.getId());
                chargeUserCoupon.setUseTime(new Date());
                chargeUserCoupon.setStatus(UserCouponStatusEnum.EXPIRED.getStatus());
                chargeUserCouponMapper.use(chargeUserCoupon);
                couponRespDto.setStatus(UserCouponStatusEnum.EXPIRED.getStatus());
            }
            couponRespDtoList.add(couponRespDto);
        }
        PageResult<List<UserCouponPageRespDto>> listPageResult = PageResult.setData((int) page.getTotal(), page.getPageSize(), page.getPages(), page.getPageNum(), couponRespDtoList);
        return CommonResult.success(listPageResult);
    }

    @Override
    public CommonResult<UserCouponRespDto> findById(Long id) {
        RBucket<ChargeUserCoupon> bucket = redissonClient.getBucket(RedisConstantPre.COUPON_CONDITION_PRE + id);
        ChargeUserCoupon chargeUserCoupon = bucket.get();
        if (chargeUserCoupon == null) {
            chargeUserCoupon = chargeUserCouponMapper.selectByPrimaryKey(id);
            if (chargeUserCoupon == null) {
                //防止缓存穿透，存放一个空对象
                bucket.set(new ChargeUserCoupon());
                log.error("优惠券id={} 不存在", id);
                return CommonResult.failed(ResultCode.PARAMETER_ERROR);
            }
        }
        //防止缓存穿透
        if (chargeUserCoupon.getId() == null) {
            log.error("缓存穿透 优惠券id={} 不存在", id);
            return CommonResult.failed(ResultCode.PARAMETER_ERROR);
        }
        ChargeUserLoginResDto user = UserCacheUtil.getUser();

        if (!Objects.equals(chargeUserCoupon.getUserId(), user.getId())) {
            log.error("操作有误，id={} 不存在", id);
            return CommonResult.failed(ResultCode.PARAMETER_ERROR);
        }

        UserCouponRespDto userCouponRespDto = new UserCouponRespDto();
        BeanUtils.copyProperties(userCouponRespDto, chargeUserCoupon);
        return CommonResult.success(userCouponRespDto);
    }

    /**
     * 统计用户优惠券数量
     *
     * @return 数量
     */
    @Override
    public CommonResult<Integer> count() {
        ChargeUserLoginResDto user = UserCacheUtil.getUser();
        int count = chargeUserCouponMapper.count(user.getId());
        return CommonResult.success(count);
    }
}
